require 'java'

def collect_files(list, file)
  if File.directory?(file) 
    Dir.foreach(file) do |child| 
      next if child == '.' || child == '..'
      path = file + '/' + child
      collect_files(list, path)
    end
    list << file
  else
    list << file
  end
end

def recursive_delete(dir)
  return unless File.exist?(dir)
  list = []
  collect_files(list, dir)
  print "deleting #{list.size} file(s)..."
  list.each do |file|
    if File.directory?(file)
      Dir.rmdir(file)
    else
      if File.delete(file) != 1
        print "FAILED\n"
        fail
      end
    end
  end
  print "done\n"
end

def junit(test_class_dir, class_path)
  
  # Append the class_dir to the class_path, if necessary
  class_path += [test_class_dir] unless class_path.member?(test_class_dir)
  
  # Make sure class_dir has trailing slash
  unless test_class_dir[-1,1] == '/'
    test_class_dir = test_class_dir + '/'
  end
  
  # Scan class_dir for test class files
  class_names = []
  FileList["#{test_class_dir}/**/*Test.class"].each do |class_file|
    class_names << class_file[test_class_dir.length, class_file.length - test_class_dir.length - '.class'.length].gsub('/', '.')
  end
  fail "No test classes found" if class_names.empty?
  
  # Load the test classes via a new classloader
  classes = load_classes(class_path, class_names)
  
  # Run the tests
  runner = org.junit.runner.JUnitCore.new
  runner.add_listener(org.junit.internal.runners.TextListener.new)
  result = runner.run(to_java_array(java.lang.Class, classes))
  fail("Unit tests failed") unless result.was_successful
end

def load_classes(class_path, class_names)

  # Make sure directories have a trailing slash, otherwise URLClassLoader ignores them
  class_path.each { |element| element << '/' if FileTest.directory?(element) && element[-1,1] != '/' }
  
  # Convert classpath elements to URLs
  urls = to_java_array(java.net.URL, class_path.map { |element| java.net.URL.new('file:' + element) })
  
  # Create a class loader for the specified class path
  loader = java.net.URLClassLoader.new(urls)  
  return class_names.map { |class_name| loader.loadClass(class_name) }
  
end

def javac(src_dir, dest_dir, classpath)
  Dir::mkdir(dest_dir) unless File::exist?(dest_dir)

  java_files = get_out_of_date_files(src_dir, dest_dir)
  
  unless java_files.empty?
    print "compiling #{java_files.size} java file(s)..."

    args = ['-Xlint', '-classpath', classpath.join(java.io.File.pathSeparator), '-d', dest_dir, *java_files]

    buf = java.io.StringWriter.new
    if com.sun.tools.javac.Main.compile(to_java_array(java.lang.String, args), java.io.PrintWriter.new(buf)) != 0
      print "FAILED\n\n"
      print buf.to_s
      print "\n"
      fail 'Compile failed'
    end
    print "done\n"
  end
end

def get_out_of_date_files(src_dir, dest_dir)
  java_files = []
  FileList["#{src_dir}/**/*.java"].each do |java_file|
    class_file = dest_dir + java_file[src_dir.length, java_file.length - src_dir.length - '.java'.length] + '.class'
    
    # todo: figure out why File.ctime doesn't work
    unless File.exist?(class_file) && java.io.File.new(class_file).lastModified > java.io.File.new(java_file).lastModified
      java_files << java_file
    end
  end
  return java_files
end

def to_java_array(element_type, ruby_array)
  java_array = java.lang.reflect.Array.newInstance(element_type, ruby_array.size)
  ruby_array.each_index { |i| java_array[i] = ruby_array[i] }
  return java_array
end

